#include <WProgram.h>
#include <Bounce/Bounce.h>
#include <Bounce/ObservableBounce.h>
#include <Messenger/Messenger.h>
#include <Metro/Metro.h>
#include <Wire/Wire.h>
#include <NewSoftSerial/NewSoftSerial.h>
#include <Flash/Flash.h>

#include "Thermostat.h"
#include "LCD.h"
#include "LED.h"
#include "View.h"
#include "TemperatureView.h"
#include "RelayView.h"
#include "ButtonPad.h"

#define LCD_TX_PIN 2
#define LCD_RX_PIN 3 //Don't use the RX Pin
#define DOOR_SWITCH_PIN 5
#define LED_BLUE_PIN 9
#define LED_GREEN_PIN 10
#define LED_RED_PIN 11
#define THERMOSTAT_RELAY_PIN 13
#define BUTTON_LED_PIN 4
#define BUTTON_1_PIN 6
#define BUTTON_2_PIN 7
#define BUTTON_3_PIN 8

#define PRINT(x) Serial.print(x); Serial.print(' ');

void processMessage();
void printOut();

FLASH_STRING(startupStr, "Kegerator");
FLASH_STRING(initStr, "Done with init");

ButtonPad buttonPad = ButtonPad(BUTTON_LED_PIN, BUTTON_1_PIN, BUTTON_2_PIN, BUTTON_3_PIN);

// Door sensor
ObservableBounce doorSwitch = ObservableBounce(DOOR_SWITCH_PIN, 10);

//temp
Metro tempSampleTimer = Metro(ONE_SECOND, 0, true); //Temperature is checked every second

// thermostat control
Metro relayEventTimer = Metro(MIN_OFF_TIME, 0, false);
Thermostat thermostat = Thermostat(&tempSampleTimer, &relayEventTimer);

// input messages
Messenger message = Messenger();

// LCD output
NewSoftSerial lcdSerial = NewSoftSerial(LCD_RX_PIN, LCD_TX_PIN);
LCD lcd = LCD(&lcdSerial);
TemperatureView tempView = TemperatureView(&lcd, &thermostat, &buttonPad);
RelayView relayView = RelayView(&lcd, &thermostat, &relayEventTimer);
//View *activeView = tempView;
View *viewList[] = {&tempView, &relayView};
int activeViewIndex;
#define NUM_VIEWS 2;
Metro viewChangeTimer = Metro(ONE_SECOND*10, 0, true);

// Internal light
LED led = LED(LED_RED_PIN, LED_GREEN_PIN, LED_BLUE_PIN, &doorSwitch,
    &thermostat);

// Debug output
//Metro debugOutputTimer = Metro(ONE_SECOND * 10, 0, true);

//unsigned long last;

void setup() {
  Serial.begin(57600);
  Serial.println(startupStr);
  message.attach(processMessage);

  pinMode(DOOR_SWITCH_PIN, INPUT);
  digitalWrite(DOOR_SWITCH_PIN, HIGH);

  lcdSerial.begin(9600);
  lcd.clearDisplay();
  lcd.setCursorLine1();
  lcd.print(startupStr);

  buttonPad.init();

  viewList[activeViewIndex]->setActive(true);

  // init temp sensor
  Wire.begin();

  // Define listeners here.  Could do this in each constructor, but better to have top level view.
  doorSwitch.addObserver(&led);
  thermostat.addObserver(&led);
  thermostat.addObserver(&tempView);
  thermostat.addObserver(&relayView);
  tempSampleTimer.addObserver(&thermostat);
  tempSampleTimer.addObserver(&relayView);
  buttonPad.addObserver(&tempView);

  led.init();

  Serial.println(initStr);
  lcd.setCursorLine2();
  lcd.print(initStr);
  delay(1000);
  //last = millis();
}

void loop() {

  // Process serial port command
  while (Serial.available()) {
    message.process(Serial.read());
  }
  doorSwitch.update();

  // Check for new button presses
  // Observers will be notified upon button activity
  buttonPad.update();

  if (tempSampleTimer.check()) { //Observers will be notified when timer runs over
    printOut();
  }

  if (buttonPad.risingEdge()) {
    if (buttonPad.read() == BUTTON_RIGHT) {
      viewList[activeViewIndex]->setActive(false);
      lcd.clearDisplay();
      activeViewIndex = (activeViewIndex-1)%NUM_VIEWS;
    } else if (buttonPad.read() == BUTTON_LEFT) {
      viewList[activeViewIndex]->setActive(false);
      lcd.clearDisplay();
      activeViewIndex = (activeViewIndex+1)%NUM_VIEWS;
    }
    viewList[activeViewIndex]->setActive(true);
    viewList[activeViewIndex]->update(&buttonPad);
  }

}

void processMessage() {
  thermostat.setTargetTemp(message.readDouble());
  if (message.available()) {
    thermostat.setTempRange(message.readDouble());
  }
  if (message.available() && message.readInt()) {
    relayEventTimer.force();
  }
}

void printOut() {
  long time = millis();
  long timeout = relayEventTimer.getTimeoutMillis();

  PRINT(time);
  PRINT(thermostat.getCurrentTemp());
  PRINT(thermostat.getTargetTemp());
  PRINT(thermostat.getTempError());
  PRINT(thermostat.getTempRange());
  PRINT(thermostat.getRelay());
  PRINT((timeout-time)/1000);
  PRINT(digitalRead(DOOR_SWITCH_PIN));
  PRINT(doorSwitch.read());
  Serial.print("\n");
}

